//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Policy Injection Application Block
//===============================================================================

using System;
using System.Collections;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.Unity.InterceptionExtension;
using EntLibContrib.PolicyInjection.CallHandlers.Configuration;
using EntLibContrib.PolicyInjection.CallHandlers.Properties;

namespace EntLibContrib.PolicyInjection.CallHandlers
{
	/// <summary>
	/// An <see cref="ICallHandler"/> that synchronizes access to the next handler.
	/// </summary>
	[ConfigurationElementType(typeof(ThreadSafeCallHandlerData))]
	public class ThreadSafeCallHandler : ICallHandler
	{
		#region Fields
		private static readonly object SyncRootProperty = new object();
		private string syncRootName;
		private int order;
		#endregion

		#region Properties
		/// <summary>
		/// Gets the SyncRootName used by this handler.
		/// </summary>
		/// <value>SyncRootName.</value>
		public string SyncRootName
		{
			get { return syncRootName; }
		}
		#endregion

		#region Construction
		/// <summary>
		/// Creates a new <see cref="ThreadSafeCallHandler"/>.
		/// </summary>
		/// <param name="syncRootName">Name of the sync root.</param>
		public ThreadSafeCallHandler(string syncRootName)
		{
			this.syncRootName = syncRootName;
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ThreadSafeCallHandler"/> class.
		/// </summary>
		/// <param name="syncRootName">Name of the sync root.</param>
		/// <param name="order">Order in which handler will be executed.</param>
		public ThreadSafeCallHandler(string syncRootName, int order)
			: this(syncRootName)
		{
			this.order = order;
		}
		#endregion

		#region Protected Methods
		/// <summary>
		/// Gets the sync root.
		/// </summary>
		/// <param name="input">The input.</param>
		/// <returns></returns>
		protected virtual object GetSyncRoot(IMethodInvocation input)
		{
			IDictionary instanceContext = MethodInvocationExtensions.GetInstanceContext(input);

			if (instanceContext == null)
				throw new InvocationContextException(Resources.InvocationContextRequired);

			Key syncRootKey = new Key(EqualityType.ReferenceEquals, SyncRootProperty, input.Target, SyncRootName);

			object syncRoot = null;

			lock (instanceContext.SyncRoot)
			{
				syncRoot = instanceContext[syncRootKey];

				if (syncRoot == null)
				{
					syncRoot = new object();
					instanceContext.Add(syncRootKey, syncRoot);
				}
			}

			return syncRoot;
		}
		#endregion

		#region ICallHandler Members
		/// <summary>
		/// Processes the method call.
		/// </summary>
		/// <param name="input"><see cref="IMethodInvocation"/> with information about the call.</param>
		/// <param name="getNext">delegate to call to get the next handler in the pipeline.</param>
		/// <returns>Return value from the target.</returns>
		/// <remarks>This handler locks the access to the next handler based on the SyncRootName.</remarks>
		public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
		{
			IMethodReturn result = null;

			object syncRoot = GetSyncRoot(input);

			lock (syncRoot)
			{
				result = getNext()(input, getNext);
			}

			return result;
		}

		/// <summary>
		/// Order in which the handler will be executed
		/// </summary>
		/// <value></value>
		public int Order
		{
			get
			{
				return order;
			}
			set
			{
				order = value;
			}
		}
		#endregion
	}
}
